#region
/* Adoor.NET - Object/Relational mapping framework leveraging ADO.NET`s powerful disconnected mode.
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *  
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *  
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 */
#endregion

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using Adoor.Expression;
using Adoor.Expression.AST;
using Adoor.Object.Domain;
using Adoor.Object.Entity;
using Adoor.Object.Query;
using System.Transactions;
using System.Drawing.Design; 

namespace Adoor.Object.Disconnected
{
    /// <summary>
    /// An object serving as a bridge between a <see cref="ObjectContext.LocalStorage"/> and one or several
    /// <see cref="ObjectContext.RemoteSources"/>.
    /// </summary>
    /// <remarks>An <see cref="ObjectContext"/> object serves as a starting point in the class library.</remarks>
    /// <example>A possible way to instantiate a new object context on a SQL server database may look like this:
    /// <code>
    ///using Adoor.Object;
    ///using Adoor.Object.Relational;
    ///using Adoor.Relational.RelationalSource;
    /// 
    ///public ObjectContext CreateContext(string connectionString, string domain)
    ///{
    ///	DataSetObjectStorage localStorage = new DataSetObjectStorage();
    ///	localStorage.RootDomain = domain;
    /// 
    ///	SqlObjectSource remoteSource = new SqlObjectSource();
    ///	remoteSource.ConnectionString = connectionString;
    ///	remoteSource.RootDomain = domain;
    /// 
    ///	ObjectContext result = new ObjectContext();
    ///	result.Domain = domain;
    ///	result.LocalStorage = localStorage;
    ///	result.RemoteSources.Add(remoteSource);
    ///	return result;
    ///}
    /// </code>
    /// </example>
    [ Designer("System.Windows.Forms.Design.ListBoxDesigner, System.Design, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a"),Editor("Microsoft.VSDesigner.Data.Design.DataTableEditor, Microsoft.VSDesigner, Version=7.0.5000.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a", typeof(UITypeEditor))]
    public class ObjectContext : Component, IServiceProvider
    {
        public ObjectContext()
        {
            this.linkedDomainCollection = new LinkedDomainCollection();
            this.objectResolver = new LinkedDomainObjectResolver(this.linkedDomainCollection);
            this.linkedDomainCollection.ObjectResolver = this.objectResolver;
        }

        //protected static log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType);

        private IObjectStorage localStorage;
        private bool disconnected;

        object IServiceProvider.GetService(Type type)
        {
            if (type == null)
                throw new ArgumentNullException("type");
            
            if (type.IsAssignableFrom(this.GetType()))
                return this;
            return null;
        }

        private static IContextStorage contextStorage = null;
        public static IContextStorage ContextStorage
        {
            get { return contextStorage; }
            set { contextStorage = value; }
        }


        private LinkedDomainCollection linkedDomainCollection;
        private LinkedDomainObjectResolver objectResolver;
        
        public IObjectResolver ObjectResolver
        {
            get { return objectResolver; }
        }

        [DataSysDescription("The Collections that holds Domains Group"), DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
        public LinkedDomainCollection LinkedDomainCollection
        {
            
            get { return linkedDomainCollection; }
        }

        /// <summary>
        /// The <see cref="IObjectStorage"/> object which contains the in-process temporary data.
        /// </summary>
        public IObjectStorage LocalStorage
        {
            get { return this.localStorage; }
            set
            {
                if (this.localStorage != null)
                    this.localStorage.ServiceProvider = null;
                this.localStorage = value;

                if (this.localStorage != null)
                {
                    this.localStorage.ServiceProvider = this;
                    this.LocalStorage.ObjectResolver = this.objectResolver;
                }
            }
        }

        /// <summary>
        /// When navigating from entity to entity using the dot notation or the foreach instruction, indicates
        /// whether data should be automatically fetched from the <see cref="RemoteSources"/> (Disconnected = false)
        /// or not.
        /// </summary>
        /// <value>When Disconnected is true, the <see cref="RemoteSources"/> are not used in lazy-loading methods.</value>
        public bool Disconnected
        {
            get { return this.disconnected; }
            set { this.disconnected = value; }
        }

        /// <overloads>Execute a query against the <see cref="LocalStorage"/>.</overloads>
        /// <summary>
        /// Execute a query against the <see cref="LocalStorage"/>.
        /// </summary>
        /// <remarks>Before the execution of the query, perform a <see cref="Preload"/> to update the data in the <see cref="LocalStorage"/>.</remarks>
        /// <param name="dataset">A DataSet object receiving the result of the query</param>
        /// <param name="type">The type to base the query on.</param>
        public void Fill(DataSet dataset, Type type)
        {
            Fill(dataset, new ObjectQuery(this.objectResolver.GetPath(type)), (object[])null);
        }

        /// <summary>
        /// Execute a query against the <see cref="LocalStorage"/>.
        /// </summary>
        /// <remarks>Before the execution of the query, perform a <see cref="Preload"/> to update the data in the <see cref="LocalStorage"/>.</remarks>
        /// <param name="dataset">A DataSet object receiving the result of the query</param>
        /// <param name="query">A string containing the object query.</param>
        /// <param name="args">An array of parameters that are passed along with the query.</param>
        public void Fill(DataSet dataset, string query, params object[] args)
        {
            Fill(dataset, this.LinkedDomainCollection.ParseQuery(query), args);
        }

        /// <summary>
        /// Execute a query against the <see cref="LocalStorage"/>.
        /// </summary>
        /// <remarks>Before the execution of the query, perform a <see cref="Preload"/> to update the data in the <see cref="LocalStorage"/>.</remarks>
        /// <param name="dataset">A DataSet object receiving the result of the query</param>
        /// <param name="query">A string containing the object query.</param>
        /// <param name="args">An array of parameters that are passed along with the query.</param>
        public void Fill(DataSet dataset, string query, IDictionary<string, object> args)
        {
            Fill(dataset, this.LinkedDomainCollection.ParseQuery(query), args);
        }

        /// <summary>
        /// Execute a query against the <see cref="LocalStorage"/>.
        /// </summary>
        /// <remarks>Before the execution of the query, perform a <see cref="Preload"/> to update the data in the <see cref="LocalStorage"/>.</remarks>
        /// <param name="dataset">A DataSet object receiving the result of the query</param>
        /// <param name="query">A <see cref="ObjectQuery"/> object representing the query.</param>
        /// <param name="args">An array of parameters that are passed along with the query.</param>
        public void Fill(DataSet dataset, ObjectQuery query, IDictionary<string, object> args)
        {
            if (!this.Disconnected)
                this.Preload(query.Path, args);
            this.LocalStorage.Fill(dataset, query, args);
        }

        /// <summary>
        /// Execute a query against the <see cref="LocalStorage"/>.
        /// </summary>
        /// <remarks>Before the execution of the query, perform a <see cref="Preload"/> to update the data in the <see cref="LocalStorage"/>.</remarks>
        /// <param name="dataset">A DataSet object receiving the result of the query</param>
        /// <param name="query">A <see cref="ObjectQuery"/> object representing the query.</param>
        /// <param name="args">An array of parameters that are passed along with the query.</param>
        public void Fill(DataSet dataset, ObjectQuery query, object[] args)
        {
            if (!this.Disconnected)
                this.Preload(query.Path, args);
            this.LocalStorage.Fill(dataset, query, args);
        }

        /// <overloads>Execute a query against the <see cref="LocalStorage"/>.</overloads>
        /// <summary>
        /// Execute a query against the <see cref="LocalStorage"/>.
        /// </summary>
        /// <remarks>Before the execution of the query, perform a <see cref="Preload"/> to update the data in the <see cref="LocalStorage"/>.</remarks>
        /// <param name="dataset">A DataSet object receiving the result of the query</param>
        /// <param name="tableName">The name of the DataTable object in the dataset receiving the result of the query</param>
        /// <param name="type">The type to base the query on.</param>
        public void Fill(DataSet dataset, string tableName, Type type)
        {
            Fill(dataset, tableName, new ObjectQuery(this.objectResolver.GetPath(type)), (object[])null);
        }

        /// <summary>
        /// Execute a query against the <see cref="LocalStorage"/>.
        /// </summary>
        /// <remarks>Before the execution of the query, perform a <see cref="Preload"/> to update the data in the <see cref="LocalStorage"/>.</remarks>
        /// <param name="dataset">A DataSet object receiving the result of the query</param>
        /// <param name="tableName">The name of the DataTable object in the dataset receiving the result of the query</param>
        /// <param name="query">A string containing the object query.</param>
        /// <param name="args">An array of parameters that are passed along with the query.</param>
        public void Fill(DataSet dataset, string tableName, string query, params object[] args)
        {
            Fill(dataset, tableName, this.LinkedDomainCollection.ParseQuery(query), args);
        }

        /// <summary>
        /// Execute a query against the <see cref="LocalStorage"/>.
        /// </summary>
        /// <remarks>Before the execution of the query, perform a <see cref="Preload"/> to update the data in the <see cref="LocalStorage"/>.</remarks>
        /// <param name="dataset">A DataSet object receiving the result of the query</param>
        /// <param name="tableName">The name of the DataTable object in the dataset receiving the result of the query</param>
        /// <param name="query">A string containing the object query.</param>
        /// <param name="args">An array of parameters that are passed along with the query.</param>
        public void Fill(DataSet dataset, string tableName, string query, IDictionary<string, Correlation> args)
        {
            Fill(dataset, tableName, this.LinkedDomainCollection.ParseQuery(query), args);
        }

        /// <summary>
        /// Execute a query against the <see cref="LocalStorage"/>.
        /// </summary>
        /// <remarks>Before the execution of the query, perform a <see cref="Preload"/> to update the data in the <see cref="LocalStorage"/>.</remarks>
        /// <param name="dataset">A DataSet object receiving the result of the query</param>
        /// <param name="tableName">The name of the DataTable object in the dataset receiving the result of the query</param>
        /// <param name="query">A <see cref="ObjectQuery"/> object representing the query.</param>
        /// <param name="args">An array of parameters that are passed along with the query.</param>
        public void Fill(DataSet dataset, string tableName, ObjectQuery query, object[] args)
        {
            if (!this.Disconnected)
                this.Preload(query.Path, args);
            this.LocalStorage.Fill(dataset, tableName, query, args);
        }

        /// <summary>
        /// Execute a query against the <see cref="LocalStorage"/>.
        /// </summary>
        /// <remarks>Before the execution of the query, perform a <see cref="Preload"/> to update the data in the <see cref="LocalStorage"/>.</remarks>
        /// <param name="dataset">A DataSet object receiving the result of the query</param>
        /// <param name="tableName">The name of the DataTable object in the dataset receiving the result of the query</param>
        /// <param name="query">A <see cref="ObjectQuery"/> object representing the query.</param>
        /// <param name="args">An array of parameters that are passed along with the query.</param>
        public void Fill(DataSet dataset, string tableName, ObjectQuery query, IDictionary<string, object> args)
        {
            if (!this.Disconnected)
                this.Preload(query.Path, args);
            this.LocalStorage.Fill(dataset, tableName, query, args);
        }

        /// <overloads>Execute a query against the <see cref="LocalStorage"/>.</overloads>
        /// <summary>
        /// Execute a query against the <see cref="LocalStorage"/>.
        /// </summary>
        /// <remarks>Before the execution of the query, perform a <see cref="Preload"/> to update the data in the <see cref="LocalStorage"/>.</remarks>
        /// <param name="table">A DataTable object receiving the result of the query</param>
        /// <param name="type">The type to base the query on.</param>
        public void Fill(DataTable table, Type type)
        {
            Fill(table, new ObjectQuery(this.objectResolver.GetPath(type)), (object[])null);
        }


        /// <summary>
        /// Execute a query against the <see cref="LocalStorage"/>.
        /// </summary>
        /// <remarks>Before the execution of the query, perform a <see cref="Preload"/> to update the data in the <see cref="LocalStorage"/>.</remarks>
        /// <param name="table">A DataTable object receiving the result of the query</param>
        /// <param name="query">A string containing the object query.</param>
        /// <param name="args">An array of parameters that are passed along with the query.</param>
        /// <returns>A <see cref="DataSet"/> containing the result of the query.</returns>
        public void Fill(DataTable table, string query, params object[] args)
        {
            Fill(table, this.LinkedDomainCollection.ParseQuery(query), args);
        }

        /// <summary>
        /// Execute a query against the <see cref="LocalStorage"/>.
        /// </summary>
        /// <remarks>Before the execution of the query, perform a <see cref="Preload"/> to update the data in the <see cref="LocalStorage"/>.</remarks>
        /// <param name="table">A DataTable object receiving the result of the query</param>
        /// <param name="query">A string containing the object query.</param>
        /// <param name="args">An array of parameters that are passed along with the query.</param>
        /// <returns>A <see cref="DataSet"/> containing the result of the query.</returns>
        public void Fill(DataTable table, string query, IDictionary<string, object> args)
        {
            Fill(table, this.LinkedDomainCollection.ParseQuery(query), args);
        }

        public void Fill(DataTable table, ObjectQuery query, IDictionary<string, object> dicArgs)
        {
            if (!this.Disconnected)
                this.Preload(query.Path, dicArgs);
            this.LocalStorage.Fill(table, query, dicArgs);
        }

        /// <summary>
        /// Execute a query against the <see cref="LocalStorage"/>.
        /// </summary>
        /// <remarks>Before the execution of the query, perform a <see cref="Preload"/> to update the data in the <see cref="LocalStorage"/>.</remarks>
        /// <param name="table">A DataTable object receiving the result of the query</param>
        /// <param name="query">A <see cref="ObjectQuery"/> object representing the query.</param>
        /// <param name="args">An array of parameters that are passed along with the query.</param>
        /// <returns>A <see cref="DataSet"/> containing the result of the query.</returns>
        public void Fill(DataTable table, ObjectQuery query, object[] args)
        {
            if (!this.Disconnected)
                this.Preload(query.Path, args);
            this.LocalStorage.Fill(table, query, args);
        }

        /// <overloads>
        /// Return the number of rows that would be returned by the equivalent <see cref="Fill"/> method.
        /// </overloads>
        /// <summary>
        /// Return the number of rows that would be returned by the equivalent <see cref="Fill"/> method.
        /// </summary>
        /// <param name="type">The type to base the query on.</param>
        /// <returns>The row count corresponding to the query.</returns>
        public Int64 GetCount(Type type)
        {
            if (!this.Disconnected)
                this.Preload(type);
            return this.LocalStorage.GetCount(type);
        }

        /// <summary>
        /// Return the number of rows that would be returned by the equivalent <see cref="Fill"/> method.
        /// </summary>
        /// <param name="query">A string containing the object query.</param>
        /// <param name="args">An array of parameters that are passed along with the query.</param>
        /// <returns>The row count corresponding to the query.</returns>
        public Int64 GetCount(string query, IDictionary<string, object> args)
        {
            return GetCount(this.LinkedDomainCollection.ParseQuery(query), args);
        }

        /// <summary>
        /// Return the number of rows that would be returned by the equivalent <see cref="Fill"/> method.
        /// </summary>
        /// <param name="query">A <see cref="ObjectQuery"/> object representing the query.</param>
        /// <param name="args">An array of parameters that are passed along with the query.</param>
        /// <returns>The row count corresponding to the query.</returns>
        public Int64 GetCount(ObjectQuery query, IDictionary<string, object> args)
        {
            if (!this.Disconnected)
                this.Preload(query.Path, args);
            return this.LocalStorage.GetCount(query, args);
        }

        /// <summary>
        /// Return the number of rows that would be returned by the equivalent <see cref="Fill"/> method.
        /// </summary>
        /// <param name="query">A <see cref="ObjectQuery"/> object representing the query.</param>
        /// <param name="args">An array of parameters that are passed along with the query.</param>
        /// <returns>The row count corresponding to the query.</returns>
        public Int64 GetCount(ObjectQuery query, object[] args)
        {
            if (!this.Disconnected)
                this.Preload(query.Path, args);
            return this.LocalStorage.GetCount(query, args);
        }

        /// <summary>
        /// Return the number of rows that would be returned by the equivalent <see cref="Fill"/> method.
        /// </summary>
        /// <param name="query">A string containing the object query.</param>
        /// <param name="args">An array of parameters that are passed along with the query.</param>
        /// <returns>The row count corresponding to the query.</returns>
        public Int64 GetCount(string query, params object[] args)
        {
            return GetCount(this.LinkedDomainCollection.ParseQuery(query), args);
        }

        /// <overloads>
        /// Execute a query against the <see cref="LocalStorage"/>.
        /// </overloads>
        /// <summary>
        /// Execute a query against the <see cref="LocalStorage"/>.
        /// </summary>
        /// <remarks>Before the execution of the query, perform a <see cref="Preload"/> to update the data in the <see cref="LocalStorage"/>.</remarks>
        /// <param name="type">The type to base the query on.</param>
        /// <returns>A <see cref="IDataReader"/> object opened to enumerate the result of the query.</returns>
        public IDataReader GetReader(Type type)
        {
            if (!this.Disconnected)
                this.Preload(type);
            return this.LocalStorage.GetDataReader(type);
        }

        /// <summary>
        /// Execute a query against the <see cref="LocalStorage"/>.
        /// </summary>
        /// <remarks>Before the execution of the query, perform a <see cref="Preload"/> to update the data in the <see cref="LocalStorage"/>.</remarks>
        /// <param name="query">A <see cref="ObjectQuery"/> object representing the query.</param>
        /// <param name="args">An array of parameters that are passed along with the query.</param>
        /// <returns>A <see cref="IDataReader"/> object opened to enumerate the result of the query.</returns>
        public IDataReader GetReader(ObjectQuery query, IDictionary<string, object> args)
        {
            if (!this.Disconnected)
                this.Preload(query.Path, args);
            return this.LocalStorage.GetDataReader(query, args);
        }

        /// <summary>
        /// Execute a query against the <see cref="LocalStorage"/>.
        /// </summary>
        /// <remarks>Before the execution of the query, perform a <see cref="Preload"/> to update the data in the <see cref="LocalStorage"/>.</remarks>
        /// <param name="query">A <see cref="ObjectQuery"/> object representing the query.</param>
        /// <param name="args">An array of parameters that are passed along with the query.</param>
        /// <returns>A <see cref="IDataReader"/> object opened to enumerate the result of the query.</returns>
        public IDataReader GetReader(ObjectQuery query, object[] args)
        {
            if (!this.Disconnected)
                this.Preload(query.Path, args);
            return this.LocalStorage.GetDataReader(query, args);
        }

        /// <summary>
        /// Execute a query against the <see cref="LocalStorage"/>.
        /// </summary>
        /// <remarks>Before the execution of the query, perform a <see cref="Preload"/> to update the data in the <see cref="LocalStorage"/>.</remarks>
        /// <param name="query">A string containing the object query.</param>
        /// <param name="args">An array of parameters that are passed along with the query.</param>
        /// <returns>A <see cref="IDataReader"/> object opened to enumerate the result of the query.</returns>
        public IDataReader GetReader(string query, IDictionary<string, object> args)
        {
            return GetReader(this.LinkedDomainCollection.ParseQuery(query), args);
        }

        /// <summary>
        /// Execute a query against the <see cref="LocalStorage"/>.
        /// </summary>
        /// <remarks>Before the execution of the query, perform a <see cref="Preload"/> to update the data in the <see cref="LocalStorage"/>.</remarks>
        /// <param name="query">A string containing the object query.</param>
        /// <param name="args">An array of parameters that are passed along with the query.</param>
        /// <returns>A <see cref="IDataReader"/> object opened to enumerate the result of the query.</returns>
        public IDataReader GetReader(string query, params object[] args)
        {
            return GetReader(this.LinkedDomainCollection.ParseQuery(query), args);
        }

        /// <overloads>
        /// Return the entity with a given oid.
        /// </overloads>
        /// <summary>
        /// Return the entity of type <c>baseType</c> with the given <c>oid</c>.
        /// </summary>
        /// <remarks>When the entity is not found in the <see cref="LocalStorage"/> and <see cref="Disconnected"/> is false, then entity is also fetched in the <see cref="RemoteSources"/>.</remarks>
        /// <param name="baseType">The type (or a base type in case of polymorphism) of the entity to return.</param>
        /// <param name="oid">The object ID of the entity to return.</param>
        /// <returns>The entity corresponding to the oid (or null if not found).</returns>
        public object Find(Type baseType, string oid)
        {
            ObjectDomain domain = this.LinkedDomainCollection.FindDomainForType(baseType);
            EntityData entityData = domain.EntityInfos[baseType];
            if (entityData == null)
                throw new Exception("entity info not found for type " + baseType.Name);

            //			EntityData actualEntityData = entityData.GetActualEntityData(oid);
            //			if (actualEntityData == null)
            //				throw new Exception("Actual entity data not found for " + baseType.Name + "(oid = " + oid + ")");
            //			return InternalFind(actualEntityData, oid);
            return Find(entityData, oid);
        }

        public T Find<T>(Type baseType, string oid)
        {
            EntityData entityData = this.LinkedDomainCollection.FindEntityDataForType(baseType);
            if (entityData == null)
                throw new Exception("entity info not found for type " + baseType.Name);

            //			EntityData actualEntityData = entityData.GetActualEntityData(oid);
            //			if (actualEntityData == null)
            //				throw new Exception("Actual entity data not found for " + baseType.Name + "(oid = " + oid + ")");
            //			return InternalFind(actualEntityData, oid);
            return Find<T>(entityData, oid);
        }

        public T Find<T>(string oid)
        {
            Type baseType = typeof(T);
            EntityData entityData = this.LinkedDomainCollection.FindEntityDataForType(baseType);
            if (entityData == null)
                throw new Exception("entity info not found for type " + baseType.Name);

            //			EntityData actualEntityData = entityData.GetActualEntityData(oid);
            //			if (actualEntityData == null)
            //				throw new Exception("Actual entity data not found for " + baseType.Name + "(oid = " + oid + ")");
            //			return InternalFind(actualEntityData, oid);
            return Find<T>(entityData, oid);
        }



        /// <summary>
        /// Return the entity with a given oid.
        /// </summary>
        /// <remarks>When the entity is not found in the <see cref="LocalStorage"/> and <see cref="Disconnected"/> is false, then entity is also fetched in the <see cref="RemoteSources"/>.</remarks>
        /// <param name="entityData">The entity info describing the type (or a base type in case of polymorphism) of the entity to return.</param>
        /// <param name="oid">The object ID of the entity to return.</param>
        /// <returns>The entity corresponding to the oid (or null if not found).</returns>
        public object Find(EntityData entityData, string oid)
        {
            if (oid == null || oid == "")
                return null;

            //EntityData actualEntityData = entityData.OidData.GetActualEntityData(oid);
            EntityData actualEntityData = entityData.GetActualEntityData(oid);
            if (actualEntityData == null)
                throw new Exception("Actual entity data not found for type " + entityData.EntityType.Name);


            IEntityFactory factory = (IEntityFactory)actualEntityData.GetService(typeof(IEntityFactory));
            IEntity result = factory.Find(this.LocalStorage, oid);
            if (result != null && result.State.Status == EntityStatus.Deleted)
                return null;

            if (result == null && !this.Disconnected)
            {
                IEntityLoader loader = (IEntityLoader)actualEntityData.GetService(typeof(IEntityLoader));
                loader.Load(this, oid);
                result = factory.Find(this.LocalStorage, oid);
            }
            return result;
        }

        public T Find<T>(EntityData entityData, string oid)
        {
            if (oid == null || oid == "")
                return default(T);

            //EntityData actualEntityData = entityData.OidData.GetActualEntityData(oid);
            EntityData actualEntityData = entityData.GetActualEntityData(oid);
            if (actualEntityData == null)
                throw new Exception("Actual entity data not found for type " + entityData.EntityType.Name);


            IEntityFactory factory = (IEntityFactory)actualEntityData.GetService(typeof(IEntityFactory));
            IEntity result = factory.Find(this.LocalStorage, oid);
            if (result != null && result.State.Status == EntityStatus.Deleted)
                return default(T);

            if (result == null && !this.Disconnected)
            {
                IEntityLoader loader = (IEntityLoader)actualEntityData.GetService(typeof(IEntityLoader));
                loader.Load(this, oid);
                result = factory.Find(this.LocalStorage, oid);
            }
            return (T)result;
        }

        /// <summary>
        /// Return a <see cref="ICollection"/> of entities of the <see cref="LocalStorage"/> resulting from executing the query given as a parameter.
        /// </summary>
        /// <remarks>Before the execution of the query, perform a <see cref="Preload"/> to update the data in the <see cref="LocalStorage"/>.</remarks>
        /// <param name="query">A string containing the object query.</param>
        /// <param name="args">An array of parameters that are passed along with the query.</param>
        /// <returns>A collection of entity corresponding to the query</returns>
        public System.Collections.ICollection Collect(string query, IDictionary<string, object> args)
        {
            return Collect(this.LinkedDomainCollection.ParseQuery(query), args);
        }

        /// <overloads>
        /// Return a <see cref="ICollection"/> of entities of the <see cref="LocalStorage"/> resulting from executing the query given as a parameter.
        /// </overloads>
        /// <summary>
        /// Return a <see cref="ICollection"/> of entities of the <see cref="LocalStorage"/> resulting from executing the query given as a parameter.
        /// </summary>
        /// <remarks>Before the execution of the query, perform a <see cref="Preload"/> to update the data in the <see cref="LocalStorage"/>.</remarks>
        /// <param name="type">The type to base the query on.</param>
        /// <returns>A collection of entity corresponding to the query</returns>
        public System.Collections.ICollection Collect<T>()
        {
            return Collect(typeof(T));
        }


        /// <overloads>
        /// Return a <see cref="ICollection"/> of entities of the <see cref="LocalStorage"/> resulting from executing the query given as a parameter.
        /// </overloads>
        /// <summary>
        /// Return a <see cref="ICollection"/> of entities of the <see cref="LocalStorage"/> resulting from executing the query given as a parameter.
        /// </summary>
        /// <remarks>Before the execution of the query, perform a <see cref="Preload"/> to update the data in the <see cref="LocalStorage"/>.</remarks>
        /// <param name="type">The type to base the query on.</param>
        /// <returns>A collection of entity corresponding to the query</returns>
        public System.Collections.ICollection Collect(Type type)
        {
            if (!this.Disconnected)
                this.Preload(type);
            return this.LocalStorage.Collect(type);
        }

        /// <summary>
        /// Return a <see cref="ICollection"/> of entities of the <see cref="LocalStorage"/> resulting from executing the query given as a parameter.
        /// </summary>
        /// <remarks>Before the execution of the query, perform a <see cref="Preload"/> to update the data in the <see cref="LocalStorage"/>.</remarks>
        /// <param name="query">A <see cref="ObjectQuery"/> object representing the query.</param>
        /// <param name="args">An array of parameters that are passed along with the query.</param>
        /// <returns>A collection of entity corresponding to the query</returns>
        public System.Collections.ICollection Collect(ObjectQuery query, IDictionary<string, object> args)
        {
            if (!this.Disconnected)
                this.Preload(query.Path, args);
            return this.LocalStorage.Collect(query, args);
        }

        /// <summary>
        /// Return a <see cref="ICollection"/> of entities of the <see cref="LocalStorage"/> resulting from executing the query given as a parameter.
        /// </summary>
        /// <remarks>Before the execution of the query, perform a <see cref="Preload"/> to update the data in the <see cref="LocalStorage"/>.</remarks>
        /// <param name="query">A <see cref="ObjectQuery"/> object representing the query.</param>
        /// <param name="args">An array of parameters that are passed along with the query.</param>
        /// <returns>A collection of entity corresponding to the query</returns>
        public System.Collections.ICollection Collect(ObjectQuery query, object[] args)
        {
            if (!this.Disconnected)
                this.Preload(query.Path, args);
            return this.LocalStorage.Collect(query, args);
        }


        /// <summary>
        /// Return a <see cref="ICollection"/> of entities of the <see cref="LocalStorage"/> resulting from executing the query given as a parameter.
        /// </summary>
        /// <remarks>Before the execution of the query, perform a <see cref="Preload"/> to update the data in the <see cref="LocalStorage"/>.</remarks>
        /// <param name="query">A string containing the object query.</param>
        /// <param name="args">An array of parameters that are passed along with the query.</param>
        /// <returns>A collection of entity corresponding to the query</returns>
        public System.Collections.ICollection Collect(string query, params object[] args)
        {
            return Collect(this.LinkedDomainCollection.ParseQuery(query), args);
        }

        /// <summary>
        /// Return a <see cref="IEnumerable"/> of entities of the <see cref="LocalStorage"/> resulting from executing the query given as a parameter.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <returns></returns>
        public System.Collections.Generic.IEnumerable<T> Enumerate<T>()
        {
            if (!this.Disconnected)
                this.Preload(typeof(T));
            return this.LocalStorage.Collect(typeof(T)) as System.Collections.Generic.IEnumerable<T>;
        }

        /// <summary>
        /// Return a <see cref="IEnumerable"/> of entities of the <see cref="LocalStorage"/> resulting from executing the query given as a parameter.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="query">A string containing the object query.</param>
        /// <param name="args">An array of parameters that are passed along with the query.</param>
        /// <returns>An enumerable of entity corresponding to the query</returns>
        public System.Collections.Generic.IEnumerable<T> Enumerate<T>(string query, params object[] args)
        {
            return Collect(this.LinkedDomainCollection.ParseQuery(query), args) as System.Collections.Generic.IEnumerable<T>;
        }


        public System.Collections.Generic.IList<T> CreateList<T>()
        {
            return new System.Collections.Generic.List<T>(Enumerate<T>()).AsReadOnly();
        }

        public System.Collections.Generic.IList<T> CreateList<T>(string query, params object[] args)
        {
            return new System.Collections.Generic.List<T>(Collect(this.LinkedDomainCollection.ParseQuery(query), args) as System.Collections.Generic.IEnumerable<T>).AsReadOnly();
        }
        private void validateAll()
        {
            foreach (LinkedDomainGroup domainGroup in LinkedDomainCollection)
                domainGroup.Validate(this.LocalStorage, this);
        }

        /// <summary>
        /// Send all the changes in the <see cref="LocalStorage"/> to the <see cref="RemoteSources"/>.
        /// </summary>
        public void Update()
        {
            validateAll();

            //what would happend if a transaction is already spawn and RequireNew
            //should we use dependent transaction ? 
            // RequireNew may lead to incosistency with commited database statements and rollbacked Objectstorage ?
            TransactionScopeOption scopeOptions = TransactionScopeOption.Required;
 
            //should we add custom config or rely on machine.conf ??? 
            TransactionOptions options = new TransactionOptions();
            options.IsolationLevel = System.Transactions.IsolationLevel.ReadCommitted;//config ??
            options.Timeout = new TimeSpan(0,0,30); //config ??

            //auto or full ??
            EnterpriseServicesInteropOption enterpriseOption = EnterpriseServicesInteropOption.Automatic;

            TransactionManager.DistributedTransactionStarted += new TransactionStartedEventHandler(TransactionManager_DistributedTransactionStarted);
                
            using (TransactionScope ts = new TransactionScope(scopeOptions,options, enterpriseOption))
            {
                    
                //Enlist LocalStorage in transactional update process
                this.LocalStorage.Enlist();
                foreach (LinkedDomainGroup domainGroup in LinkedDomainCollection)
                {
                    domainGroup.Update(this);
                }
                ts.Complete();
            }
        
        }

        void TransactionManager_DistributedTransactionStarted(object sender, TransactionEventArgs e)
        {
            Trace.TraceInformation("Escalating transaction '{0}' : starting 2PC ", e.Transaction.TransactionInformation.LocalIdentifier);
            Trace.Flush();

        }


        /// <overloads>
        /// Preload data from the <see cref="RemoteSources"/> into the <see cref="LocalStorage"/> according to a given object path.
        /// </overloads>
        /// <summary>
        /// Preload data from the <see cref="RemoteSources"/> into the <see cref="LocalStorage"/> according to a given object path.
        /// </summary>
        /// <param name="type">The type to base the object path on.</param>
        public void Preload(Type type)
        {
            this.Preload(this.objectResolver.GetPath(type), null, null);
        }

        /// <summary>
        /// Preload data from the <see cref="RemoteSources"/> into the <see cref="LocalStorage"/> according to a given object path.
        /// </summary>
        /// <param name="text">The string describing the data path to preload.</param>
        /// <param name="args">An array of parameters that are passed along with to the object path.</param>
        public void Preload(string text, params object[] args)
        {
            ObjectPath path = ObjectPath.Parse(ObjectResolver, text);
            Preload(path, args);
        }

        /// <summary>
        /// Preload data from the <see cref="RemoteSources"/> into the <see cref="LocalStorage"/> according to a given object path.
        /// </summary>
        /// <param name="path">A <see cref="ObjectPath"/> object representing the query.</param>
        /// <param name="args">An array of parameters that are passed along with the query.</param>
        public void Preload(ObjectPath path, IDictionary<string, object> args)
        {
            this.Preload(path, args, null);
        }

        /// <summary>
        /// Preload data from the <see cref="RemoteSources"/> into the <see cref="LocalStorage"/> according to a given object path.
        /// </summary>
        /// <param name="path">A <see cref="ObjectPath"/> object representing the query.</param>
        /// <param name="args">An array of parameters that are passed along with the query.</param>
        public void Preload(ObjectPath path, object[] args)
        {
            this.Preload(path, args, null);
        }

        private void Preload(ObjectPath path, object args, string[] globallyLoadableReferences)
        {
            // The alreadyFetched table will contain correlations that have already been fetched
            // It will be kept up-to-date to perform traversal of the correlations in topological order
            Dictionary<string, Correlation> alreadyFetched = new Dictionary<string, Correlation>(StringComparer.CurrentCultureIgnoreCase);

            // First, it is filled with correlations we don't want to preload
            foreach (Correlation c in path.Correlations.Values)
                if (c.Excluded)
                    alreadyFetched.Add(c.Name, c);

            // A reference may be partially or globally loaded. When a reference is globally loaded, each source entity is tagged and
            // no SQL is emitted again when navigating from one of these source entities through the reference.
            // This may boost performance (but don't misuse it, otherwise the context would be desynchronised)
            // The globallyLoaded table contains those references that should be marked as globally loaded.
            IDictionary<string, Correlation> globallyLoaded;
            if (globallyLoadableReferences == null)
                globallyLoaded = GloballyLoadedEvaluator.Evaluate(path);
            else
            {
                globallyLoaded = new Dictionary<string, Correlation>(StringComparer.CurrentCultureIgnoreCase);
                // Initialize with manually provided information. This is error-prone, it's a possibility left in case our algorithm happens to be too conservative
                if (globallyLoadableReferences != null)
                    foreach (string alias in globallyLoadableReferences)
                        globallyLoaded.Add(alias, path.Correlations[alias]);
            }
            if (AdoorLoger.IsDebugEnabled)
            {
                AdoorLoger.Debug("Begin preload");
                string correlations = "";
                foreach (string alias in alreadyFetched.Keys)
                {
                    if (correlations != "")
                        correlations += ", ";
                    correlations += alias;
                }
                AdoorLoger.Debug("Excluded correlations: (" + correlations + ")");
                string references = "";
                foreach (string alias in globallyLoaded.Keys)
                {
                    if (!alreadyFetched.ContainsKey(alias))
                    {
                        if (references != "")
                            references += ", ";
                        references += alias;
                    }

                }
                AdoorLoger.Debug("Globally loadable references: (" + references + ")");
            }

            // The entities to fetch are those specified in the query minus those present in alreadyFetched
            // The references to fetch are deduced from the entities to fetch : a reference is to be fetched only
            // when both its source and target entites are to be fetched
            // The referencesToFetch table contains thoses references to be fetched
            IDictionary<string, Correlation> referencesToFetch = new Dictionary<string, Correlation>(StringComparer.CurrentCultureIgnoreCase);
            foreach (Correlation correlation in path.Correlations.Values)
                if (correlation.Source != null)
                {
                    if (!alreadyFetched.ContainsKey(correlation.Name)
                        || !alreadyFetched.ContainsKey(correlation.Source.Name))
                        referencesToFetch.Add(correlation.Name, correlation);
                }
            foreach (Correlation correlation in path.Correlations.Values)
                TopologicalPreload(path, args, correlation, alreadyFetched, referencesToFetch, globallyLoaded);


            AdoorLoger.Debug("End preload");
        }

        private void TopologicalPreload(ObjectPath path, object args, Correlation correlation, IDictionary<string, Correlation> alreadyFetched, IDictionary<string, Correlation> referencesToFetch, IDictionary<string, Correlation> globallyLoaded)
        {
            // Does the correlation comes from a join ?
            if (correlation.Source != null)
            {
                TopologicalPreload(path, args, correlation.Source, alreadyFetched, referencesToFetch, globallyLoaded); // Topological order on correlations
                if (referencesToFetch.ContainsKey(correlation.Name))
                {
                    ReferenceData referenceData = correlation.ReferenceData;
                    bool markReference = globallyLoaded.ContainsKey(correlation.Name);
                    IReferenceReader reader = (IReferenceReader)referenceData.GetService(typeof(IReferenceReader));
                    // TODO: mark references around here
                    reader.FetchReference(this, path, args, correlation.Source.Name, correlation.Name, markReference);
                }
            }

            if (!alreadyFetched.ContainsKey(correlation.Name))
            {
                IEntityLoader loader = (IEntityLoader)correlation.EntityData.GetService(typeof(IEntityLoader));
                loader.Load(this, correlation.Name, path, args);
                alreadyFetched[correlation.Name] = correlation;
            }
        }

        /// <summary>
        /// Compute a table of globally loadable references inside an object path.
        /// A globally loadable reference is a reference with no further restriction in an inner join or in the where clause.
        /// </summary>
        private class GloballyLoadedEvaluator : IObjectPathEvaluator
        {
            IDictionary<string, Correlation> globallyLoaded = new Dictionary<string, Correlation>(StringComparer.CurrentCultureIgnoreCase);

            public static IDictionary<string, Correlation> Evaluate(ObjectPath path)
            {
                GloballyLoadedEvaluator gle = new GloballyLoadedEvaluator();
                path.EvaluateFrom(gle);
                return gle.globallyLoaded;
            }

            public void EvaluateScan(ScanPath a)
            {
                // nothing
            }

            private static void RemoveCorrelation(IDictionary<string, Correlation> globallyLoaded, Correlation c)
            {
                while (globallyLoaded.ContainsKey(c.Name))
                {
                    globallyLoaded.Remove(c.Name);
                    if (c.Source != null && !c.IsOuterJoin)
                        c = c.Source;
                }
            }

            public void EvaluateJoin(JoinPath j)
            {
                bool oldOuterJoin = InOuterJoin;
                InOuterJoin = j.IsOuterJoin;
                try
                {
                    j.InnerQuery.EvaluateFrom(this);
                    Correlation c = (Correlation)j.Correlations[j.Alias];
                    this.globallyLoaded.Add(j.Alias, c);
                    if (c.Source != null && !c.IsOuterJoin)
                        RemoveCorrelation(this.globallyLoaded, c.Source);
                }
                finally
                {
                    InOuterJoin = oldOuterJoin;
                }
            }

            public void EvaluateCross(CrossPath j)
            {
                bool oldOuterJoin = InOuterJoin;
                InOuterJoin = j.IsOuterJoin;
                try
                {
                    j.Source1.EvaluateFrom(this);
                    j.Source2.EvaluateFrom(this);
                }
                finally
                {
                    InOuterJoin = oldOuterJoin;
                }
            }

            public void EvaluateCrossQuery(CrossPathQuery j)
            {
                bool oldOuterJoin = InOuterJoin;
                InOuterJoin = j.IsOuterJoin;
                try
                {
                    j.Source1.EvaluateFrom(this);
                    //j.Source2Query.EvaluateFrom(this);
                }
                finally
                {
                    InOuterJoin = oldOuterJoin;
                }
            }

            public void EvaluateScanQuery(ScanPathQuery s)
            {
            }

            public void EvaluateFilter(FilterPath f)
            {
                f.InnerQuery.EvaluateFrom(this);
                RestrictionEvaluator.Evaluate(f.Condition, this.globallyLoaded);
            }

            public void EvaluateIntersect(IntersectPath i)
            {
                i.Source1.EvaluateFrom(this);
                Correlation c = (Correlation)this.globallyLoaded[i.Correlation1];
                if (c != null)
                    RemoveCorrelation(this.globallyLoaded, c);
            }

            // if a correlation name is part of an expression, remove the correlation from "globallyLoaded"
            private class RestrictionEvaluator : ExpressionNavigator
            {
                public static void Evaluate(ExpressionNode en, IDictionary<string, Correlation> globallyLoaded)
                {
                    RestrictionEvaluator ev = new RestrictionEvaluator(globallyLoaded);
                    en.Accept(ev);
                }

                private IDictionary<string, Correlation> globallyLoaded;
                private RestrictionEvaluator(IDictionary<string, Correlation> globallyLoaded)
                {
                    this.globallyLoaded = globallyLoaded;
                }

                public override void VisitName(ExpressionName n)
                {
                    string name = n.Qualifier != "" ? n.Qualifier : n.Value;
                    Correlation c;
                    if(this.globallyLoaded.TryGetValue(name, out c))
                        GloballyLoadedEvaluator.RemoveCorrelation(this.globallyLoaded, c);
                }

                public override void VisitFunction(ExpressionFunction f)
                {
                    // nothing
                }
            }

            private bool inOuterJoin = false;

            public bool InOuterJoin
            {
                get { return inOuterJoin; }
                set { inOuterJoin = value; }
            }
        }

        public static event RemoteSourceRetryEventHandler RemoteSourceRetry;

        private static bool OnRemoteSourceRetry(object sender, bool retry, Exception ex, out bool handled)
        {
            handled = false;
            RemoteSourceRetryEventArgs e = new RemoteSourceRetryEventArgs(retry, ex);
            if (RemoteSourceRetry != null)
            {
                foreach (Delegate dlg in RemoteSourceRetry.GetInvocationList())
                {
                    dlg.DynamicInvoke(new object[] { sender, e });
                    if (e.Handled)
                    {
                        handled = true;
                        break;
                    }
                }
            }
            return e.Retry;
        }


        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
            if (LocalStorage != null)
                LocalStorage.Dispose();
        }



      
    }

    public delegate void RemoteSourceRetryEventHandler(object sender, RemoteSourceRetryEventArgs e);

    public class RemoteSourceRetryEventArgs : EventArgs
    {
        public RemoteSourceRetryEventArgs(bool retry, Exception ex)
        {
            this.retry = retry;
            this.ex = ex;
            this.handled = false;
        }

        private bool retry;
        public bool Retry
        {
            get { return retry; }
            set { retry = value; }
        }

        private bool handled;
        public bool Handled
        {
            get { return handled; }
            set { handled = value; }
        }

        private Exception ex;
        public Exception Exception
        {
            get { return ex; }
        }
    }
}
